// 分配内核存储空间的头文件
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/fs.h>

// cdev.h 中已经包含了
// 用于自动把设备挂载到 /dev/ 下面
#include <linux/device.h>

// 分配内核内存空间的头文件
#include <linux/cdev.h>

// 导入信号量
#include <linux/semaphore.h>

// 导入互斥锁
// 互斥锁就是信号量为1的特殊情况，是信号量的一种
#include <linux/mutex.h>

#include "sema_sync.h"

/**
 * 声明了
 * copy_to_user()      // 主要用于 read 函数
 * copy_from_user()    // 主要用于 write 函数
 */
#include <linux/uaccess.h>

#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2

int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t hello_dev_t;
int hello_nr_devs = HELLO_NR_DEVS;

module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);

/**
 * 实际的字符设备结构体，类似于面向对象的继承
 */
struct My_character_dev
{
    struct cdev cdev;
    // 自定义的属性和变量
    // 定义 char 指针使得可以保存任意字符串
    char *c;

    // 指定字符串的长度
    int length;

    // 引入信号量
    // 生产者信号量
    struct semaphore empty_count;

    // 消费者信号量
    struct semaphore fill_count;

    // 引入互斥锁，用于控制共享资源 c、length 的临界资源访问
    struct mutex lock;
};

// 定义结构体指针
struct My_character_dev *my_char_dev;

// 额外创建一个指针
struct class *my_char_class;

// 定义字符设备的操作函数

int my_char_open(struct inode *inode, struct file *fp)
{

    printk(KERN_INFO "open my_character_dev %d %d \n", iminor(inode), MINOR(inode->i_cdev->dev));

    // 1、获取设备结构体的地址
    // inode->i_cdev 指向的就是 My_character_dev.cdev

    // 把 My_character_dev.cdev 指针转换为 其外部结构体指针
    // 第一个参数：cdev 指针
    // 第二个参数：外部容器结构体是什么类型
    // 第三个参数：cdev 在外部容器结构体内的名字
    struct My_character_dev *my_dev = container_of(inode->i_cdev, struct My_character_dev, cdev);

    // 2、将设备结构体地址放到文件描述符结构的私有数据中
    //    read() write() 函数都需要这个结构体 来获得字符设备的地址
    fp->private_data = my_dev;

    // 每次打开时都重置游标位置
    fp->f_pos = 0;

    return 0;
}

/**
 * @param buf 用户空间的缓冲区，也就是要把字符设备的内容保存到的地方
 * @param count 用户要拷贝数据的个数
 * @param f_ops 文件描述的偏移量
 * @return 读取的数据量
 */
ssize_t my_char_read(struct file *fp, char __user *buf, size_t count, loff_t *f_ops)
{
    printk(KERN_INFO "read my_character_dev\n");
    // 将字符设备中的字符串给到用户空间中
    //  获得在 open() 函数中 放入的 设备结构体地址
    struct My_character_dev *my_dev = fp->private_data;

    // 本次共计读取了多少个数据
    ssize_t retval = 0;

    if (*f_ops == 0)
    {
        /**
         * 信号量是不可重入的
         * read() 函数会被多次调用
         * 第一次： 返回还剩多少个字符未被读取
         * 最后一次：这个函数要返回0代表所有数据已经被全部读取完成
         * 所以信号量的P操作只能执行一次
         */

        // 获取信号量
        if (down_interruptible(&my_dev->fill_count))
        {
            /**
             * 可中断函数只是可选的（但推荐使用）​，由于它们可以被信号中断，所以应该检查它们的返回值。
             * 非零值意味着睡眠被某种信号中断，驱动程序应该返回ERESTARTSYS。
             *
             */
            return -ERESTARTSYS;
        }
    }
    // 访问临界资源获取互斥锁
    /**
     * 调用mutex_lock()时要非常小心，只有能够保证无论在什么情况下互斥锁都会释放时才可以使用它。
     * 在用户上下文中，建议始终使用mutex_lock_ interruptible()来获取互斥锁，
     * 因为mutex_lock()即使收到信号（甚至是Ctrl+C组合键）​，也不会返回。
     *
     */
    if (mutex_lock_interruptible(&my_dev->lock))
    {
        return -ERESTARTSYS;
    }

    if (*f_ops >= my_dev->length)
    {
        /** 
         * 已经读取到尽头
         * 到这里，一个完整的P操作才算完成
         *  
         * 
         * 信号量V操作，唤醒生产者进程
         */
        up(&my_dev->empty_count);
        
        goto end;
    }

    if (*f_ops + count > my_dev->length)
    {
        // 用户想读取的内容比 字符设备自身持有的数据还多
        // 减少用户想读取的内容数量
        retval = my_dev->length - *f_ops;
    }

    if (copy_to_user(buf, my_dev->c, retval))
    {
        retval = -EFAULT;
        goto end;
    }

    // 更新读取的偏移量
    *f_ops += retval;

end:
    // 无论如何 一定要 释放互斥锁
    mutex_unlock(&my_dev->lock);

    return retval;
}

/**
 * 返回值是写入的字节数（长度）​。
 */
ssize_t my_char_write(struct file *fp, const char __user *buf, size_t count, loff_t *f_ops)
{

    printk(KERN_INFO "write my_character_dev %lu count\n", count);

    struct My_character_dev *my_dev = fp->private_data;

    // 信号量 生产者 P操作
    // P操作（获取信号量）成功会返回0
    // 失败会返回 -EINTR
    if (down_interruptible(&my_dev->empty_count))
    {
        // 重新获取信号量
        return -ERESTARTSYS;
    }

    // 获取互斥锁 访问临界资源
    // 成功获取互斥锁会返回0
    // 失败会返回 -EINTR
    if (mutex_lock_interruptible(&my_dev->lock))
    {
        return -ERESTARTSYS;
    }

    if (my_dev->c)
    {
        kfree(my_dev->c);
    }

    my_dev->c = NULL;
    my_dev->length = 0;

    char *kernel_data;

    if ((kernel_data = kzalloc(count, GFP_KERNEL)) < 0)
    {

        printk(KERN_WARNING "can't allocate kernel memory to write %d\n", *kernel_data);
        return -ENOMEM;
    }

    unsigned long processed = 0;
    if ((processed = copy_from_user(kernel_data, buf, count)) < 0)
    {
        return -EFAULT;
    }

    my_dev->c = kernel_data;

    my_dev->length = count;

    // 释放互斥锁
    mutex_unlock(&my_dev->lock);
    // 信号量 V 操作 唤醒 消费者进程
    up(&my_dev->fill_count);

    *f_ops = processed;

    // 返回值是写入的字节数（长度）​。
    return count; // 不能返回0，否则会不停地往里面写
}

int my_char_release(struct inode *inode, struct file *fp)
{
    printk(KERN_INFO "release my_character_dev\n");
    return 0;
}

// 把函数注册到结构体中
struct file_operations my_char_file_ops = {
    .owner = THIS_MODULE,
    .read = my_char_read,
    .write = my_char_write,
    .open = my_char_open,
    .release = my_char_release};

dev_t make_dev(size_t i)
{
    return MKDEV(hello_major, hello_minor + i);
}

int __init my_char_dev_init(void)
{
    int result;
    printk(KERN_INFO "begin to register my_character_dev\n");
    if (hello_major)
    {
        hello_dev_t = MKDEV(hello_major, hello_minor);
        // 使用指定的主设备号进行分配
        result = register_chrdev_region(hello_dev_t, hello_nr_devs, "my_character_dev");
    }
    else
    {
        result = alloc_chrdev_region(&hello_dev_t, hello_minor, hello_nr_devs, "my_character_dev");
        hello_major = MAJOR(hello_dev_t);
    }

    if (result < 0)
    {
        printk(KERN_WARNING "fail to allocate my_character_device\n");
        return result;
    }

    // 给字符设备分配空间
    // GFP_KERNEL  代表在内核中分配空间
    my_char_dev = kzalloc(sizeof(struct My_character_dev) * hello_nr_devs, GFP_KERNEL);

    if (!my_char_dev)
    {
        // 避免内存泄漏，必须执行

        printk(KERN_WARNING "fail to allocate kernel memory to my_character_dev !\n");
        goto fail;
    }

    for (size_t i = 0; i < hello_nr_devs; i++)
    {

        // 初始化信号量
        sema_init(&my_char_dev[i].empty_count, 1);
        sema_init(&my_char_dev[i].fill_count, 0);

        // 初始化 互斥锁（互斥锁是信号量为1时的特殊情况）
        mutex_init(&my_char_dev[i].lock);

        // 使用指定的文件操作函数结构体来初始化字符设备
        cdev_init(&my_char_dev[i].cdev, &my_char_file_ops);
        my_char_dev[i].cdev.owner = THIS_MODULE;

        dev_t dev = make_dev(i);

        // 将该字符设备添加到系统中
        result = cdev_add(&my_char_dev[i].cdev, dev, 1);

        if (result)
        {
            printk(KERN_WARNING "fail to initialize my_character_dev %d\n", result);
            goto failure_kzalloc;
        }
    }

    // 自动挂载到 /dev/下面
    // 第二个参数是 挂载在 /sys/class 下的名字
    my_char_class = class_create("my_char_dev");

    if (!my_char_class)
    {
        printk(KERN_WARNING "fail to create class! \n");
        goto failure_kzalloc;
    }

    for (size_t i = 0; i < hello_nr_devs; i++)
    {
        dev_t dev = make_dev(i);
        // 真正创建 /dev/my_chara0 设备
        device_create(my_char_class, NULL, dev, NULL, "my_chara%lu", i);
    }

    printk(KERN_INFO "register my_character_dev successfully!\n");

    return 0;

    // failure_class:

failure_kzalloc:
    kfree(my_char_dev);
fail:
    unregister_chrdev_region(hello_dev_t, hello_nr_devs);
    return 0;
}

void __exit my_char_dev_exit(void)
{

    // 移除设备
    for (size_t i = 0; i < hello_nr_devs; i++)
    {
        dev_t dev = make_dev(i);

        device_destroy(my_char_class, dev);
    }

    class_destroy(my_char_class);

    for (size_t i = 0; i < hello_nr_devs; i++)
    {
        cdev_del(&my_char_dev[i].cdev);
    }

    kfree(my_char_dev);

    // 释放申请得到的字符设备号
    unregister_chrdev_region(hello_dev_t, hello_nr_devs);
    printk(KERN_INFO "unregister my_character_dev successfully!\n");
}

module_init(my_char_dev_init);
module_exit(my_char_dev_exit);

//  许可证描述
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("luojunhua");
MODULE_VERSION("V1.0");